/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jpa.tools.swing;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.util.List;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnitUtil;
import javax.persistence.metamodel.Attribute;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.enhance.StateManager;
/**
* A specialized Swing table to display a list of persistent entity instances.
* The data for the table is supplied via a specialized {@linkplain EntityDataModel data model}
* that is supplied at construction.
* <br>
* The table view uses specialized cell renderer to display single-valued and multi-valued
* association.
*
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class EntityTable<T> extends JTable {
private InstanceCellRenderer instanceCellRenderer;
private CollectionCellRenderer collectionCellRenderer;
public EntityTable(Class<T> cls, List<T> data, int styleBits, EntityManagerFactory unit) {
super();
instanceCellRenderer = new InstanceCellRenderer(unit.getPersistenceUnitUtil());
collectionCellRenderer = new CollectionCellRenderer();
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
setModel(new EntityDataModel<T>(cls, data, unit.getMetamodel(), styleBits));
getModel().addTableModelListener(this);
initColumnSizes();
setFillsViewportHeight(true);
}
public InstanceCellRenderer getInstanceRenderer() {
return instanceCellRenderer;
}
/**
* Gets the special renderer for single- and multi-valued association.
* Otherwise uses the super classes' renderer defined by the field type.
*/
public TableCellRenderer getCellRenderer(int row, int column) {
Attribute<?,?> attr = ((EntityDataModel)getModel()).getAttribute(column);
TableCellRenderer renderer = null;
if (attr == null) {
renderer = super.getCellRenderer(row, column);
} else if (attr.isAssociation()) {
renderer = instanceCellRenderer;
} else if (attr.isCollection()) {
renderer = collectionCellRenderer;
} else {
renderer = super.getCellRenderer(row, column);
}
if (renderer instanceof DefaultTableCellRenderer) {
((DefaultTableCellRenderer)renderer).setHorizontalAlignment(JLabel.CENTER);
}
return renderer;
}
public TableCellEditor getCellEditor(int row, int column) {
Attribute<?,?> attr = ((EntityDataModel)getModel()).getAttribute(column);
if (attr == null)
return super.getCellEditor(row, column);
if (attr.isCollection())
return new DefaultCellEditor((JComboBox)getCellRenderer(row, column));
return super.getCellEditor(row, column);
}
/**
* Picks good column sizes.
* If all column heads are wider than the column's cells'
* contents, then you can just use column.sizeWidthToFit().
*/
private void initColumnSizes() {
TableModel model = getModel();
TableColumn column = null;
Component comp = null;
int headerWidth = 0;
TableCellRenderer headerRenderer = getTableHeader().getDefaultRenderer();
for (int columnIndex = 0; columnIndex < model.getColumnCount(); columnIndex++) {
int cellWidth = 0;
column = getColumnModel().getColumn(columnIndex);
comp = headerRenderer.getTableCellRendererComponent(
null, column.getHeaderValue(),
false, false, 0, 0);
headerWidth = comp.getPreferredSize().width;
TableCellRenderer renderer = getCellRenderer(0, columnIndex);
int rowCount = Math.min(model.getRowCount(), 10);
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
Object value = model.getValueAt(rowIndex, columnIndex);
comp = renderer.getTableCellRendererComponent(
this, value,
false, false, rowIndex, columnIndex);
cellWidth = Math.max(comp.getPreferredSize().width, cellWidth);
}
column.setPreferredWidth(Math.max(headerWidth, cellWidth));
}
}
/**
* Renders the value of a persistent entity in a table column as the persistent identifier.
* The persistent identifier is extracted by the new {@link PersistenceUnitUtil utility} feature
* of JPA 2.0 API.
*
* @author Pinaki Poddar
*
*/
public class InstanceCellRenderer extends DefaultTableCellRenderer {
private final PersistenceUnitUtil util;
public InstanceCellRenderer(PersistenceUnitUtil util) {
super();
this.util = util;
}
/**
* Gets the stringified persistence identifier of the given instance.
*
*/
public String renderAsString(Object instance) {
if (instance == null) {
return "null";
}
Object id = util.getIdentifier(instance);
if (id != null)
return id.toString();
if (instance instanceof PersistenceCapable) {
PersistenceCapable pc = (PersistenceCapable)instance;
StateManager sm = pc.pcGetStateManager();
id = sm == null ? null : sm.fetchObjectId();
}
return id == null ? "null" : id.toString();
}
public void setValue(Object instance) {
setForeground(Color.BLUE);
setText(renderAsString(instance));
}
}
/**
* Renders a many-valued attribute as simply three dots.
*
* @author Pinaki Poddar
*
*/
public class CollectionCellRenderer extends DefaultTableCellRenderer {
public CollectionCellRenderer() {
setPreferredSize(new Dimension(10,20));
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
return super.getTableCellRendererComponent(table, value == null ? null : "...",
isSelected, hasFocus, row, column);
}
}
}